#include "tabsym.h"

#define DEBUG 0

#define IDX_SCOPE_GLOB 0
#define IDX_SCOPE_INST 1

typedef struct {
    Scope_level lvl;
    ht_table* tab;
} scope;

// Private functions
scope* ts_top(tabsym* ts);
bool id_isconstant(char const* s);

tabsym* ts_create() {
	return (tabsym*)ll_create();
}

tabsym* ts_delete(tabsym* ts) {
	// Pop and delete all scopes
	ll_delete(ts);
	return NULL;
}

Symbol* ts_lookup_top(tabsym* ts, char const* nom) {
	scope* top = ts_top(ts);
	return (Symbol*)ht_get(top->tab, nom);
}

Symbol* ts_lookup(tabsym* ts, char const* nom) {
	// TODO: not effective
	int i, size = ll_size(ts);
	scope* sc;
	Symbol* t;

    Nature nat = id_getnature(nom);
    debug_print("Looking for var '%s' with nature %s,"
                " scope stack size: %d\n",
                nom, pp_nature(nat), size);

    if (nat == n_var_glob && size > IDX_SCOPE_GLOB) {
        scope* sc_glob = ll_get(ts, IDX_SCOPE_GLOB);
        assert(sc_glob->lvl == sclvl_glob);
        if (t = ht_get(sc_glob->tab, nom)) return t;
    } else if (nat == n_var_inst && size > IDX_SCOPE_INST) {
        scope* sc_inst = ll_get(ts, IDX_SCOPE_INST);
        assert(sc_inst->lvl == sclvl_inst);
        if (t = ht_get(sc_inst->tab, nom)) return t;
    } else {
        for (i = size - 1; i >= 0; i--) {
            sc = ll_get(ts, i);
            if (t = ht_get(sc->tab, nom)) return t;
            // if nom is loc, never peel of an fun-scope to find it
            if (nat == n_var_loc && sc->lvl == sclvl_fun) break;
            debug_print("Peeling scope %d,%s\n", i, pp_scope_level(sc->lvl));
        }
    }

    debug_print("Found nothing%s\n", "");

	return (Symbol*)NULL;
}

Symbol* ts_add(tabsym* ts, char const* nom, Type t) {
	debug_print("Add %s with type: %s\n", nom, pp_type(t));

	Symbol* sym = (Symbol*)malloc(sizeof(Symbol));
	sym->type = t;
	sym->constant = id_isconstant(nom);

    // TODO:
	// if prefixed by @ -> instance scope
	// if prefixed by $ -> global scope
    Nature n = id_getnature(nom);
    scope* sc_tgt = NULL;
    if (n == n_var_loc) {
        sc_tgt = ts_top(ts);
    } else if (n == n_var_glob) {
        sc_tgt = ll_get(ts, IDX_SCOPE_GLOB);
    } else if (n == n_var_inst) {
        sc_tgt = ll_get(ts, IDX_SCOPE_INST);
    }
    debug_print("adding variable with n %s to sc with n %s\n"
                , pp_nature(n), pp_scope_level(sc_tgt->lvl));
    /*
     * assert(sc_tgt->nat == n
     *        || (sc_tgt->lvl == sclvl_glob && n == n_var_loc));
     */


    ht_set(sc_tgt->tab, nom, sym);

    return sym;
}

void ts_pushs(tabsym* ts, Scope_level lvl) {
	scope* new = (scope*)malloc(sizeof(scope));
    new->lvl = lvl;
    new->tab = ht_create();
	ll_push(ts, new);
}

void ts_pops(tabsym* ts) {
	// TODO memory management
	ll_pop(ts);
}

scope* ts_top(tabsym* ts) {
	return (scope*)ll_last(ts);
}


char const* pp_scope_level(Scope_level n) {
	switch (n) {
		case sclvl_loc: return "n_var_loc";
		case sclvl_inst: return "n_var_inst";
		case sclvl_glob: return "n_var_glob";
		default: return "not a nature";
	}
}
